//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include <regs.h>
#include <bsp.h>
#include <init.h>

#include "exception.h"

const uint_t    c_uMaxNumberOfInterrupts = 8;
const uint_t    c_uNumberOfIpls          = IPL31 + 1;

typedef struct IsrDescriptor
{
    ipl_t   ipl;
    isr_t   isr;
    void    *pvDevice;
} IsrDescriptor;

EXTERN ipl_t            g_currentIpl;
EXTERN irqs_t           g_irqsTable[c_uNumberOfIpls + 1];
EXTERN irqs_t           g_activeIrqs;
EXTERN IsrDescriptor    g_isrDescriptors[c_uMaxNumberOfInterrupts];

ipl_t           g_currentIpl = IPL_Base;
irqs_t          g_irqsTable[c_uNumberOfIpls + 1];
irqs_t          g_activeIrqs = 0;
IsrDescriptor   g_isrDescriptors[c_uMaxNumberOfInterrupts];

EXTERN_C unsigned long uExceptionHandlers[32] = {0};
EXTERN_C void IRQHandler();
EXTERN_C void BpHandler();
EXTERN_C void CacheHandler();
EXTERN_C void TlbHandler();
EXTERN_C void TlbHandlerDouble();
EXTERN_C void GenericHandler();
EXTERN_C void RiHandler();
EXTERN_C void CopHandler();
EXTERN_C void FpuHandler();
EXTERN_C void SysHandler();
EXTERN_C void TLBModHandler();
EXTERN_C void AdeHandler();
#ifdef _XINHAO
EXTERN_C void MachineCheck();
#endif

DECL_ASMLINKAGE void ExceptTlb();
DECL_ASMLINKAGE void ExceptCache();
DECL_ASMLINKAGE void ExceptGeneric();

DECL_ASMLINKAGE void TlbDebug();

STATIC DECL_CODEINIT void CDECL InitVectorTable()
{
    // Some firmware leaves the BEV flag set, clear it.
    uint32_t    uStatus;
    read_cp0_register($12, uStatus);
    uStatus = uStatus & (~SR_BEV) & (~SR_SR);
    write_cp0_register($12, uStatus);

    // Copy the generic exception handler code to it's final destination.
    memcpy((void *)(K0BASE + 0x0), (void *)ExceptTlb, 0x80);
    memcpy((void *)(K1BASE + 0x100), (void *)ExceptCache, 0x80);
    memcpy((void *)(K0BASE + 0x180), (void *)ExceptGeneric, 0x80);

    // fill special handler, as you wish
    // if we don't set 1-5 handler, a tlb exception run into except_tlb? why
    uExceptionHandlers[1] = (unsigned long)TLBModHandler;
    uExceptionHandlers[2] = (unsigned long)TlbHandlerDouble;
    uExceptionHandlers[3] = (unsigned long)TlbHandlerDouble;
    uExceptionHandlers[4] = (unsigned long)AdeHandler;
    uExceptionHandlers[5] = (unsigned long)AdeHandler;

    uExceptionHandlers[8] = (unsigned long)SysHandler;
    uExceptionHandlers[9] = (unsigned long)BpHandler;
    uExceptionHandlers[10] = (unsigned long)RiHandler;
    uExceptionHandlers[11] = (unsigned long)CopHandler;
    uExceptionHandlers[15] = (unsigned long)FpuHandler;
#ifdef _XINHAO
    uExceptionHandlers[24] = (unsigned long)MachineCheck;
#endif
    for (uint32_t i = 1; i < 32; i++) {
        if (!uExceptionHandlers[i])
            uExceptionHandlers[i] = (unsigned long)GenericHandler;
    }

    uExceptionHandlers[0] = (unsigned long)IRQHandler;

    //
    // after write the code into the memory, must flush cache
    //
    FlushCache();
}

EXTERN DECL_CODEINIT bool_t CDECL InitInterrupts()
{
    InitSystemCallTable();

    InitVectorTable();

    BspMaskAllIrqs(FALSE);

    Sti();

    return TRUE;
}

EXTERN void CDECL DzRegisterIsr(irq_t irq, ipl_t ipl, isr_t isr,
    void *pvDevice)
{
    assert(bsp.uNumberOfIrqs > irq);
    assert(IPL31 >= ipl);
    assert(isr);

    uint32_t uFlags = SaveFlagsAndCli();

    if (NULL != g_isrDescriptors[irq].isr) {
        DzUnregisterIsr(irq);
    }

    g_isrDescriptors[irq].ipl       = ipl;
    g_isrDescriptors[irq].isr       = isr;
    g_isrDescriptors[irq].pvDevice  = pvDevice;

    for (uint_t i = 0; i <= g_isrDescriptors[irq].ipl; i++) {
        g_irqsTable[i] |= (irqs_t)1 << irq;
    }

    DzEnableIrq(irq);

    RestoreIF(uFlags);
}

EXTERN void CDECL DzUnregisterIsr(irq_t irq)
{
    assert(bsp.uNumberOfIrqs > irq);
    assert(g_isrDescriptors[irq].isr);

    uint32_t uFlags = SaveFlagsAndCli();

    DzDisableIrq(irq);

    for (uint_t i = 0; i <= g_isrDescriptors[irq].ipl; i++) {
        g_irqsTable[i] &= ~((irqs_t)1 << irq);
    }

    g_isrDescriptors[irq].isr = NULL;

    RestoreIF(uFlags);
}

EXTERN void CDECL DzEnableIrq(irq_t irq)
{
    assert(bsp.uNumberOfIrqs > irq);

    uint32_t uFlags = SaveFlagsAndCli();
    if (NULL != g_isrDescriptors[irq].isr) {
        g_activeIrqs |= (irqs_t)1 << irq;

        if (g_isrDescriptors[irq].ipl < g_currentIpl) {
            BspMaskIrqs((irqs_t)1 << irq, TRUE);
        }
    }

    RestoreIF(uFlags);
}

EXTERN void CDECL DzDisableIrq(irq_t irq)
{
    assert(bsp.uNumberOfIrqs > irq);

    uint32_t uFlags = SaveFlagsAndCli();
    if (NULL != g_isrDescriptors[irq].isr) {
        if (g_isrDescriptors[irq].ipl < g_currentIpl) {
            BspMaskIrqs((irqs_t)1 << irq, FALSE);
        }

        g_activeIrqs &= ~((irqs_t)1 << irq);
    }
    RestoreIF(uFlags);
}

EXTERN ipl_t CDECL DzRaiseCurrentIpl(ipl_t ipl)
{
    assert(IPL31 >= ipl);

    uint32_t uFlags = SaveFlagsAndCli();

    register ipl_t prevIpl = g_currentIpl;

    if (ipl < g_currentIpl) {
        BspMaskIrqs(
            (g_irqsTable[ipl] - g_irqsTable[g_currentIpl]) & g_activeIrqs,
            FALSE);
        g_currentIpl = ipl;
    }

    RestoreIF(uFlags);

    return prevIpl;
}

EXTERN ipl_t CDECL DzLowerCurrentIpl(ipl_t ipl)
{
    assert(IPL1 <= ipl && ipl <= IPL_Base);

    uint32_t uFlags = SaveFlagsAndCli();

    register ipl_t prevIpl = g_currentIpl;

    if (ipl > g_currentIpl) {
        BspMaskIrqs(
            (g_irqsTable[g_currentIpl] - g_irqsTable[ipl]) & g_activeIrqs,
            TRUE);
        g_currentIpl = ipl;
    }

    RestoreIF(uFlags);

    return prevIpl;
}

EXTERN_C void HandleInterrupt(irq_t irq, InterruptContext *pContext)
{
    assert(g_isrDescriptors[irq].isr);

    register ipl_t prevIpl = g_currentIpl;

    assert(g_isrDescriptors[irq].ipl < g_currentIpl);

    g_currentIpl = g_isrDescriptors[irq].ipl;
    BspMaskIrqs(
        (g_irqsTable[g_currentIpl] - g_irqsTable[prevIpl]) & g_activeIrqs,
        FALSE);

    BspAcknowledgeIrq(irq);
    AcknowledgeException();

    Sti();
    (*g_isrDescriptors[irq].isr)(
            irq, g_isrDescriptors[irq].pvDevice, pContext);
    Sti();

    // TODO: need optimize?
    Cli();

    assert(g_currentIpl < prevIpl);
    BspMaskIrqs(
        (g_irqsTable[g_currentIpl] - g_irqsTable[prevIpl]) & g_activeIrqs,
        TRUE);
    g_currentIpl = prevIpl;
    Sti();
}
